/**
 * impress.js
 *
 * impress.js is a presentation tool based on the power of CSS3 transforms and transitions
 * in modern browsers and inspired by the idea behind prezi.com.
 *
 * MIT Licensed.
 *
 * Copyright 2011 Bartek Szopka (@bartaz)
 */

(function ( document, window ) {

  // HELPER FUNCTIONS

  var pfx = (function () {

    var style = document.createElement('dummy').style,
    prefixes = 'Webkit Moz O ms Khtml'.split(' '),
    memory = {};

    return function ( prop ) {
      if ( typeof memory[ prop ] === "undefined" ) {

        var ucProp  = prop.charAt(0).toUpperCase() + prop.substr(1),
        props   = (prop + ' ' + prefixes.join(ucProp + ' ') + ucProp).split(' ');

        memory[ prop ] = null;
        for ( var i in props ) {
          if ( style[ props[i] ] !== undefined ) {
            memory[ prop ] = props[i];
            break;
          }
        }

      }

      return memory[ prop ];
    }

  })();

  var arrayify = function ( a ) {
    return [].slice.call( a );
  };

  var forEach = Array.prototype.forEach;
  var slice = Array.prototype.slice;
  var isArray = Array.isArray;

  var css = function ( el, props ) {
    var key, pkey;
    for ( key in props ) {
      if ( props.hasOwnProperty(key) ) {
        pkey = pfx(key);
        if ( pkey != null ) {
          el.style[pkey] = props[key];
        }
      }
    }
    return el;
  }

  var byId = function ( id ) {
    return document.getElementById(id);
  }

  var $ = function ( selector, context ) {
    context = context || document;
    return context.querySelector(selector);
  };

  var $$ = function ( selector, context ) {
    context = context || document;
    return arrayify( context.querySelectorAll(selector) );
  };

  var translate = function ( t ) {
    return " translate3d(" + t.x + "px," + t.y + "px," + t.z + "px) ";
  };

  var rotate = function ( r, revert ) {
    var rX = " rotateX(" + r.x + "deg) ",
    rY = " rotateY(" + r.y + "deg) ",
    rZ = " rotateZ(" + r.z + "deg) ";

    return revert ? rZ+rY+rX : rX+rY+rZ;
  };

  var scale = function ( s ) {
    return " scaleX(" + s.x + ") scaleY(" + s.y + ") scaleZ(" + s.z + ") ";
  }

  // CHECK SUPPORT

  var ua = navigator.userAgent.toLowerCase();
  var impressSupported = ( pfx("perspective") != null ) &&
    ( ua.search(/(iphone)|(ipod)|(ipad)|(android)/) == -1 );

  // DOM ELEMENTS

  var impress = byId("impress");

  if (!impressSupported) {
    impress.className = "impress-not-supported";
    return;
  } else {
    impress.className = "";
  }

  var canvas = document.createElement("div");
  canvas.className = "canvas";

  arrayify( impress.childNodes ).forEach(function ( el ) {
    canvas.appendChild( el );
  });
  impress.appendChild(canvas);

  var steps = $$(".step", impress);

  // SETUP
  // set initial values and defaults

  document.documentElement.style.height = "100%";

  css(document.body, {
    height: "100%",
    overflow: "hidden"
  });

  var props = {
    position: "absolute",
    transformOrigin: "top left",
    transition: "all 1s ease-in-out",
    transformStyle: "preserve-3d"
  }

  css(impress, props);
  css(impress, {
    top: "50%",
    left: "50%",
    perspective: "1000px"
  });
  css(canvas, props);

  var current = {
    translate: { x: 0, y: 0, z: 0 },
    rotate:    { x: 0, y: 0, z: 0 },
    scale:     { x: 1, y: 1, z: 1 }
  };

  steps.forEach(function ( el, idx ) {
    var data = el.dataset,
    step = {
      translate: {
        x: data.x || 0,
        y: data.y || 0,
        z: data.z || 0
      },
      rotate: {
        x: data.rotateX || 0,
        y: data.rotateY || 0,
        z: data.rotateZ || data.rotate || 0
      },
      scale: {
        x: data.scaleX || data.scale || 1,
        y: data.scaleY || data.scale || 1,
        z: data.scaleZ || 1
      }
    };

    el.stepData = step;

    if ( !el.id ) {
      el.id = "step-" + (idx + 1);
    }

    css(el, {
      position: "absolute",
      transform: "translate(-50%,-50%)" +
        translate(step.translate) +
        rotate(step.rotate) +
        scale(step.scale),
      transformStyle: "preserve-3d"
    });

  });

  // making given step active

  var active = null;

  var select = function ( el ) {
    if ( !el || !el.stepData ) {
      // selected element is not defined as step
      return false;
    }

    // Sometimes it's possible to trigger focus on first link with some keyboard action.
    // Browser in such a case tries to scroll the page to make this element visible
    // (even that body overflow is set to hidden) and it breaks our careful positioning.
    //
    // So, as a lousy (and lazy) workaround we will make the page scroll back to the top
    // whenever slide is selected
    //
    // If you are reading this and know any better way to handle it, I'll be glad to hear about it!
    window.scrollTo(0, 0);

    var step = el.stepData;

    if ( active ) {
      active.classList.remove("active");
    }
    el.classList.add("active");

    impress.className = "step-" + el.id;

    // `#/step-id` is used instead of `#step-id` to prevent default browser
    // scrolling to element in hash
    window.location.hash = "#/" + el.id;

    var target = {
      rotate: {
        x: -parseInt(step.rotate.x, 10),
        y: -parseInt(step.rotate.y, 10),
        z: -parseInt(step.rotate.z, 10)
      },
      scale: {
        x: 1 / parseFloat(step.scale.x),
        y: 1 / parseFloat(step.scale.y),
        z: 1 / parseFloat(step.scale.z)
      },
      translate: {
        x: -step.translate.x,
        y: -step.translate.y,
        z: -step.translate.z
      }
    };

    var zoomin = target.scale.x >= current.scale.x;

    css(impress, {
      // to keep the perspective look similar for different scales
      // we need to 'scale' the perspective, too
      perspective: step.scale.x * 1000 + "px",
      transform: scale(target.scale),
      transitionDelay: (zoomin ? "500ms" : "0ms")
    });

    css(canvas, {
      transform: rotate(target.rotate, true) + translate(target.translate),
      transitionDelay: (zoomin ? "0ms" : "500ms")
    });

    current = target;
    active = el;

    return el;
  }

  // EVENTS
  var setSubSteps = function (el) {
    var steps = el.querySelectorAll(".substep"),
    order = [], unordered = [];
    forEach.call(steps, function (el) {
      var index = Number(el.dataset.order);
      if (!isNaN(index)) {
        if (!order[index]) {
          order[index] = el;
        } else if (Array.isArray(order[index])) {
          order[index].push(el);
        } else {
          order[index] = [order[index], el];
        }
      } else {
        unordered.push(el);
      }
    });
    el.subSteps = order.filter(Boolean).concat(unordered);
  };

  var setPrevious = function (data) {
    if (isArray(data)) {
      data.forEach(setPrevious);
      return;
    }
    data.classList.remove('active');
    data.classList.add('previous');
  };

  var setActive = function (data) {
    if (isArray(data)) {
      data.forEach(setActive);
      return;
    }
    data.classList.remove('previous');
    data.classList.add('active');
  };

  var clearSub = function (data) {
    if (isArray(data)) {
      data.forEach(clearSub);
      return;
    }
    data.classList.remove('active');
    data.classList.remove('previous');
  };

  var nextStep = function () {
    var subactive, next, subSteps;
    if (!active.subSteps) {
      setSubSteps(active);
    }
    subSteps = active.subSteps;
    if (subSteps.length && ((subactive = subSteps.active) !==
       (subSteps.length - 1))) {
      if (subactive != null) {
        setPrevious(subSteps[subactive]);
      } else {
        subactive = -1;
      }
      setActive(subSteps[++subactive]);
      subSteps.active = subactive;
      return;
    }
    next = steps.indexOf( active ) + 1;
    next = next < steps.length ? steps[ next ] : steps[ 0 ];
    if (!next.subSteps) {
      setSubSteps(next);
    }
    if (next.subSteps.active != null) {
      forEach.call(next.subSteps, clearSub);
      next.subSteps.active = null;
    }
    select(next);
  };

  var previousStep = function () {
    var subactive, next, subSteps;
    if (!active.subSteps) {
      setSubSteps(active);
    }
    subSteps = active.subSteps;
    if (subSteps.length && ((subactive = subSteps.active) || (subactive === 0))) {
      clearSub(subSteps[subactive]);
      if (subactive) {
        setActive(subSteps[--subactive]);
        subSteps.active = subactive;
      } else {
        subSteps.active = null;
      }
      return;
    }
    next = steps.indexOf( active ) - 1;
    next = next >= 0 ? steps[ next ] : steps[ steps.length-1 ];
    if (!next.subSteps) {
      setSubSteps(next);
    }
    if (next.subSteps.length &&
       (next.subSteps.active !== (next.subSteps.length - 1))) {
      slice.call(next.subSteps, 0, -1).forEach(setPrevious);
      setActive(next.subSteps[next.subSteps.length - 1]);
      next.subSteps.active = next.subSteps.length - 1;
    }
    select(next);
  };

  document.addEventListener("keydown", function ( event ) {
    if ( event.keyCode == 9 || ( event.keyCode >= 32 && event.keyCode <= 34 ) || (event.keyCode >= 37 && event.keyCode <= 40) ) {
      var next = active;
      switch( event.keyCode ) {
      case 33: ; // pg up
      case 37: ; // left
      case 38:   // up
        previousStep();
        break;
      case 9:  ; // tab
      case 32: ; // space
      case 34: ; // pg down
      case 39: ; // right
      case 40:   // down
        nextStep();
        break;
      }

      event.preventDefault();
    }
  }, false);

  document.addEventListener("click", function ( event ) {
    // event delegation with "bubbling"
    // check if event target (or any of its parents is a link or a step)
    var target = event.target;
    while ( (target.tagName != "A") &&
      (!target.stepData) &&
      (target != document.body) ) {
      target = target.parentNode;
    }

    if ( target.tagName == "A" ) {
      var href = target.getAttribute("href");

      // if it's a link to presentation step, target this step
      if ( href && href[0] == '#' ) {
        target = byId( href.slice(1) );
      }
    }

    if ( select(target) ) {
      event.preventDefault();
    }
  });

  var getElementFromUrl = function () {
    // get id from url # by removing `#` or `#/` from the beginning,
    // so both "fallback" `#slide-id` and "enhanced" `#/slide-id` will work
    return byId( window.location.hash.replace(/^#\/?/,"") );
  }

  window.addEventListener("hashchange", function () {
    select( getElementFromUrl() );
  }, false);

  // START
  // by selecting step defined in url or first step of the presentation
  select(getElementFromUrl() || steps[0]);

})(document, window);

